iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
AI 自動化

用 AI Agent 打造你的產品使用手冊產線系列 第 9

[Day 09] 產線實作 2:用注入狀態精準控制畫面

  • 分享至 

  • xImage
  •  

昨天把元件定位這件事解決了,程式已經能穩定找到畫面上的東西。但找得到元件,不代表畫面本身每次都長一樣。今天處理截圖產線最大的敵人:狀態。

狀態的不確定性

在執行截圖腳本時,需要避免因為狀態不同而得到不同的截圖,才不會出現「圖文不符」的問題。造成狀態不同的來源主要有三種:

  1. 後端資料

    最常見的就是資料庫的資料 (e.g. 成員名單),如果沒有清理測試資料的話,每次取得的資料都有可能會不同。另外,以本次的範例目標來說,即時串流畫面也屬於這一類,每次看到的串流畫面都會不同。

  2. 本地狀態

    有些個人化的設定 (e.g. 語言、亮/暗模式) 會存在前端,不論是存在 localStoragesessionStorage,或是寫入本地的檔案,都可能會影響截圖。

  3. 時間

    這是比較容易被忽略的來源,包含:時間戳、相對時間顯示 (e.g. 3 分鐘前)、日期選擇器的預設值。

這三種來源各自需要不同的處理方法,接下來會一個一個進行討論。

後端資料:網路攔截

後端資料的處理相對簡單,可以透過 Playwright 的 page.route() 攔截網路請求:

await page.route('**/api/stream/**', (route) =>
  route.fulfill({ path: 'fixtures/stream-frame.jpg' })
)

await page.route('**/api/nodes', (route) =>
  route.fulfill({ path: 'fixtures/nodes.json' })
)

我們可以用固定的 JSON 來替換 response 中的資料,確保畫面、表格中的內容是一樣的。同理,即時串流的部分則可以改用固定圖片 (可以考慮用真實狀況截一張圖)。

詳細的說明可以參考 Playwright 文件

本地狀態:狀態注入

本地狀態的部分就要看狀態存在哪裡,然後用對應的方式覆蓋原本的值。如果是 localStorage / sessionStorage,就直接用 setItem(key, value) 處理就好了。大概長這樣:

await context.addInitScript(([kv]) => {
  for (const [key, value] of Object.entries(kv)) {
    localStorage.setItem(key, value)
  }
}, [{ locale: 'zh-Hant', role: 'operator' }])

const page = await context.newPage()
await page.goto(url)

需要注意的是,addInitScript 是掛在 context 而不是 page 上。掛在 context 上,之後開出來的每一個 page 都會執行,掛在 page 上就只有那一個。

如果注入的只有 localStorage,兩者其實差不多 (localStorage 本來就是同 context、同源共用),但像等一下時間那節要用的覆寫 Date,是對 document 打補丁,不會跨 page 共用;而且 App 自己開的視窗 (window.open、Electron 另開的 BrowserWindow),等我們拿到 handle 時通常已經跳轉完了,根本沒有補注入的機會。既然寫法一樣,就沒必要冒這個險,直接掛在 context 上,簡單又粗暴。

另外,執行狀態注入的時機有時候是很重要的 (取決於程式碼是怎麼寫的),大多數時候會建議在第一個頁面跳轉 (navigation) 之前,因為有些頁面在載入的時候就需要使用到這些本地狀態了。

至於要不要 reload,也取決於注入時機,如果有照上面提到的,在首次跳轉之前就注入,就不需要 reload。不過,由於 Electron 在 firstWindow() 拿到手時,視窗早就跳轉過了,沒有「跳轉之前」的空檔,所以只有 reload 這條路可走。

時間

時間的部分,可以在 fixture 資料裡寫死成固定值 (類似後端資料的處理方法),或是直接用更粗暴的方法:覆寫 Date 物件,讓它後續回傳的值都是固定的值。Playwright 從 1.45 開始內建了 page.clock,可以直接接管整個時間系統,不用自己手刻 addInitScript

await page.clock.install({ time: new Date('2025-01-01T09:00:00') })

裝上去之後,Date.now()new Date(),甚至 setInterval / setTimeout 的排程都會凍結在這個時間點上,畫面上的時間戳、相對時間顯示都會是固定值,不會再因為每次截圖間隔不同而跳動。

處理完狀態問題之後,接下來就可以開始討論「截圖」本身了,明天會接著討論截圖時機與截圖範圍!


上一篇
[Day 08] 產線實作 1:用 data-testid 精準定位元件
下一篇
[Day 10] 產線實作 3:截圖時機與範圍
系列文
用 AI Agent 打造你的產品使用手冊產線17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言